﻿/*******************************************************
* Copyright (c) 2018-2088, By XuXiang all rights reserved.
*
* FileName: LoginModule.cpp
* Summary: 登陆模块。
*
* Author: XuXiang
* Date: 2024-04-10 15:23
*******************************************************/

#include "LoginModule.h"
#include "LoginDefine.h"
#include <algorithm>
#include <Frame/MainBase.h>
#include <Frame/ServerBase.h>
#include <Frame/NetManagerBase.h>
#include <Config/BasicModuleConfig.h>
#include <MySQL/MySQLWrap.h>
#include <Frame/EventManager.h>
#include <Frame/StorageManager.h>
#include <Protocol/NetMsgLogin.h>
#include <Util/StringUtil.h>
#include <Util/TimeUtil.h>

namespace Competition
{
	const int LoginModule::ROLE_ID_STAMP_BIT = 1000000;

	const int LoginModule::OUTLINE_DELAY_NORMAL = 5;

	const int LoginModule::OUTLINE_DELAY_EXCEPTION = 20;

	LoginModule::LoginModule()
	{
	}

	LoginModule::~LoginModule()
	{
		m_UIDToUserID.clear();
		SAFE_DELETE_MAP(m_Users);
		SAFE_DELETE_VECTOR(m_WantQuitUsers);
	}

	void LoginModule::RegisterNetMessage(NetManagerBase *pMgr)
	{
		NET_REGISTER(pMgr, MsgID::CS_LOGIN, OnLoginRequest);
		NET_REGISTER(pMgr, MsgID::DS_GET_USER, OnGetUserResponse);
		NET_REGISTER(pMgr, MsgID::CS_QUIT, OnQuitRequest);
	}

	void LoginModule::RegisterStorageMessage(StorageManager* pMgr)
	{
		DB_REGISTER(pMgr, MsgID::SD_GET_USER, OnGetUserRequest);
		DB_REGISTER(pMgr, MsgID::SD_ADD_USER, OnAddUserRequest);
		DB_REGISTER(pMgr, MsgID::SD_UPDATE_USER_TIME, OnUpdateUserTimeRequest);
	}

	void LoginModule::AddConfig(vector<ModuleConfig*> &cfgs)
	{
		cfgs.push_back(ModuleConfig::GetInstance<BasicModuleConfig>());
	}

	void LoginModule::Init()
	{
		ModuleBase::Init();

		//读取角色创建序号
		char sql[64];
		::sprintf(sql, "call sp_select_user_max_id();");
		auto ret = GetMySQL()->Query(sql);
		if (ret->GetRecord())
		{
			UserInfo::NextRoleStamp = (ret->GetInt("max_id") % ROLE_ID_STAMP_BIT) + 1;
		}
		else
		{
			::printf("Query role stamp failed!\n");
		}
		::printf("NextRoleStamp:%d\n", UserInfo::NextRoleStamp);
	}

	void LoginModule::Release()
	{
		ModuleBase::Release();
	}

	void LoginModule::OnUpdatePerSecond()
	{
		Int64 now = TimeUtil::GetCurrentSecond();
		for (UserInfoVector::iterator itr = m_WantQuitUsers.begin(); itr != m_WantQuitUsers.end(); )
		{
			//判断是否已经到了退出时刻
			if ((*itr)->GetQuitTimeStamp() <= now)
			{
				//移除
				UserInfo *info = *itr;
				info->SetLastQuitTime(TimeUtil::GetCurrentSecond());
				itr = m_WantQuitUsers.erase(itr);
				MainBase::GetCurMain()->GetNetManager()->Close(info->GetUID());
				::printf("UserQuit name:%s rid:%d\n", info->GetNickName().c_str(), info->GetUserID());

				//更新登录退出相关时间
				SDUpdateUserTimeRequest req2;
				req2.UserID = info->GetUserID();
				req2.LastLogin = info->GetLastLoginTime();
				req2.LastQuit = info->GetLastQuitTime();
				RequestStorage(info->GetUserID(), MsgID::SD_UPDATE_USER_TIME, &req2);

				//通知退出，销毁
				EventParam *ep = EventParam::Get(LoginEvent::EID_USER_QUIT);
				ep->i1 = info->GetUserID();
				EventManager::GetInstance()->TriggerEvent(ep);
				SAFE_DELETE(info);
			}
			else
			{
				++itr;
			}
		}
	}

	UserInfo* LoginModule::GetUser(int user_id)
	{
		UserInfoMap::iterator itr = m_Users.find(user_id);
		if (itr != m_Users.end())
		{
			return itr->second;
		}
		return NULL;
	}

	UserInfo* LoginModule::GetUserByUID(Int64 uid)
	{
		UIDToUserIDMap::iterator itr = m_UIDToUserID.find(uid);
		if (itr == m_UIDToUserID.end())
		{
			return NULL;
		}
		return GetUser(itr->second);
	}

	Int64 LoginModule::GetUID(int user_id)
	{
		UserInfo* info = GetUser(user_id);
		return info == NULL ? 0 : info->GetUID();
	}

	void LoginModule::OnConnect(UInt64 uid)
	{
		::printf("LoginModule::OnConnect uid:%lld\n", uid);
	}

	void LoginModule::OnDisconnect(UInt64 uid)
	{
		::printf("LoginModule::OnDisconnect uid:%lld\n", uid);
		OnUserOutline(uid, 1);
	}

	UserInfo* LoginModule::OnVisitorUserOnline(UInt64 uid, const string& did)
	{
		//如果该did有在线的则直接踢掉
		for (UserInfoMap::iterator itr = m_Users.begin(); itr != m_Users.end(); ++itr)
		{
			UserInfo* info = itr->second;
			if (info->GetDeviceID() == did)
			{
				//把原来的连接关闭掉，更新信息，然后返回
				m_UIDToUserID.erase(info->GetUID());
				MainBase::GetCurMain()->GetNetManager()->Close(info->GetUID());
				info->SetUID(uid);
				info->SetDeviceID(did);
				m_UIDToUserID[uid] = info->GetUserID();
				return info;
			}
		}

		//查找待移除列表
		for (UserInfoVector::iterator itr = m_WantQuitUsers.begin(); itr != m_WantQuitUsers.end(); ++itr)
		{
			UserInfo* info = *itr;
			if (info->GetDeviceID() == did)
			{
				//如果原来的连接还在就关闭掉
				if (info->GetUID() != 0 && info->GetUID() != uid)
				{
					m_UIDToUserID.erase(info->GetUID());
					MainBase::GetCurMain()->GetNetManager()->Close(info->GetUID());
				}

				//更新信息，调整所在的容器，然后返回
				m_WantQuitUsers.erase(itr);
				m_Users.insert(UserInfoMap::value_type(info->GetUserID(), info));
				info->SetUID(uid);
				info->SetDeviceID(did);
				m_UIDToUserID[uid] = info->GetUserID();

				//通知上线
				EventParam* ep2 = EventParam::Get(LoginEvent::EID_USER_ONLINE);
				ep2->i1 = info->GetUserID();
				EventManager::GetInstance()->TriggerEvent(ep2);
				return info;
			}
		}

		return NULL;
	}

	void LoginModule::OnUserOutline(UInt64 uid, int type)
	{
		UserInfo* info = GetUserByUID(uid);
		if (info == NULL)
		{
			return;
		}
		::printf("OnUserOutline user:%d type:%d\n", info->GetUserID(), type);

		//被动掉线停留时间比较长，且可以重连
		int gap = type == 0 ? OUTLINE_DELAY_NORMAL : OUTLINE_DELAY_EXCEPTION;
		info->SetOutline(gap, type == 1);
		m_Users.erase(info->GetUserID());
		m_WantQuitUsers.push_back(info);

		//通知离线
		EventParam* ep = EventParam::Get(LoginEvent::EID_USER_OUTLINE);
		ep->i1 = info->GetUserID();
		EventManager::GetInstance()->TriggerEvent(ep);
	}

	void LoginModule::OnLoginRequest(NetDataItem *item)
	{
		CSLoginRequest req;
		req.Unpack(item->data, 0);
		::printf("LoginModule::OnLoginRequest type:%d device:%s\n", req.Type, req.DeviceID.c_str());

		//判断用户是否数据还在
		UserInfo* info = OnVisitorUserOnline(item->uid, req.DeviceID);
		if (info != NULL)
		{
			//登录回复
			MainBase::GetCurMain()->GetNetManager()->Update(item->uid, RemoteKey(RemoteType::RT_CLIENT, info->GetUserID()));
			SCLoginResponse res;
			res.Result = 0;
			res.UserID = info->GetUserID();
			res.NickName = info->GetNickName();
			SendNet(info->GetUserID(), MsgID::SC_LOGIN, &res);
			return;
		}
		
		//请求数据库
		SDGetUserRequest sd_req;
		sd_req.UID = item->uid;
		sd_req.Type = req.Type;
		sd_req.DeviceID = req.DeviceID;
		RequestStorage(0, MsgID::SD_GET_USER, &sd_req);
	}

	void LoginModule::OnQuitRequest(NetDataItem* item)
	{
		CSQuitRequest req;
		req.Unpack(item->data, 0);
		::printf("LoginModule::OnQuitRequest token:%lld\n", item->uid);
		OnUserOutline(item->uid, 0);

		//退出回复
		SCQuitResponse res;
		MainBase::GetCurMain()->GetNetManager()->Send(item->uid, MsgID::SC_QUIT, &res);
	}

	void LoginModule::OnGetUserRequest(NetDataItem* item)
	{
		SDGetUserRequest req;
		req.Unpack(item->data, 0);
		::printf("LoginModule::OnGetUserRequest type:%d device:%s\n", req.Type, req.DeviceID.c_str());

		//根据登录类型使用不同查询语句
		char sql[128];
		if (req.Type == 0)
		{
			::sprintf(sql, "call sp_select_user_by_devices_id('%s');", req.DeviceID.c_str());
		}
		else
		{
			::printf("The login type %d is unsupported.\n", req.Type);
		}

		//查询结果回复
		DSGetUserResponse res;
		res.UID = req.UID;
		res.Type = req.Type;
		res.DeviceID = req.DeviceID;
		auto ret = GetMySQL()->Query(sql);
		while (ret->GetRecord())
		{
			res.UserID = ret->GetInt("user_id");
			res.NickName = ret->GetString("nick_name");
			res.CreateTime = ret->GetInt64("create_time");
			res.LastLogin = ret->GetInt64("last_login");
			res.LastQuit = ret->GetInt64("last_quit");
		}
		ResponseStorage(0, MsgID::DS_GET_USER, &res);
	}

	void LoginModule::OnGetUserResponse(NetDataItem* item)
	{
		DSGetUserResponse res;
		res.Unpack(item->data, 0);
		::printf("LoginModule::OnGetUserResponse type:%d user_id:%d\n", res.Type, res.UserID);

		//创建新的用户信息(正式逻辑是先查表读取，查不到再创建新的并写表)
		UserInfo* info = new UserInfo(res.UserID != 0 ? res.UserID : UserInfo::GetNewID());
		info->SetUID(res.UID);
		info->SetDeviceID(res.DeviceID);
		if (res.UserID != 0)
		{
			//用数据库回复的初始化玩家
			info->SetNickName(res.NickName);
			info->SetCreateTime(res.CreateTime);
			info->SetLastLoginTime(res.LastLogin);
			info->SetLastQuitTime(res.LastQuit);
		}
		else
		{
			//创建新玩家
			char name_buffer[128];
			::sprintf(name_buffer, "Player_%d", info->GetUserID());
			info->SetNickName(name_buffer);
			info->SetCreateTime(TimeUtil::GetCurrentSecond());
			info->SetLastLoginTime(info->GetCreateTime());
			info->SetLastQuitTime(info->GetCreateTime());

			//请求数据库保存
			SDAddUserRequest req;
			req.UserID = info->GetUserID();
			req.Type = res.Type;
			req.DeviceID = info->GetDeviceID();
			req.NickName = info->GetNickName();
			req.CreateTime = info->GetCreateTime();
			RequestStorage(info->GetUserID(), MsgID::SD_ADD_USER, &req);
		}
		m_Users.insert(UserInfoMap::value_type(info->GetUserID(), info));
		m_UIDToUserID[res.UID] = info->GetUserID();

		//登录回复
		MainBase::GetCurMain()->GetNetManager()->Update(info->GetUID(), RemoteKey(RemoteType::RT_CLIENT, info->GetUserID()));
		SCLoginResponse res2;
		res2.Result = 0;
		res2.UserID = info->GetUserID();
		res2.NickName = info->GetNickName();
		res2.LastLogin = info->GetLastLoginTime();
		res2.LastQuit = info->GetLastQuitTime();
		SendNet(info->GetUserID(), MsgID::SC_LOGIN, &res2);

		//更新登录退出相关时间
		info->SetLastLoginTime(TimeUtil::GetCurrentSecond());
		info->SetLastQuitTime(info->GetLastLoginTime());
		SDUpdateUserTimeRequest req2;
		req2.UserID = info->GetUserID();
		req2.LastLogin = info->GetLastLoginTime();
		req2.LastQuit = info->GetLastQuitTime();
		RequestStorage(info->GetUserID(), MsgID::SD_UPDATE_USER_TIME, &req2);

		//通知上线
		EventParam* ep = EventParam::Get(LoginEvent::EID_USER_ONLINE);
		ep->i1 = info->GetUserID();
		EventManager::GetInstance()->TriggerEvent(ep);
	}

	void LoginModule::OnAddUserRequest(NetDataItem* item)
	{
		SDAddUserRequest req;
		req.Unpack(item->data, 0);
		::printf("LoginModule::OnAddUserRequest type:%d user_id:%d\n", req.Type, req.UserID);

		//保存玩家
		MySQLWrap* mysql = GetMySQL();
		char sql[128];
		//`sp_insert_user`(IN `in_user_id` int,IN `in_type` int,IN `in_device_id` varchar(64),`in_nick_name` varchar(32),`in_create_time` bigint)
		::sprintf(sql, "call sp_insert_user(%d,%d,'%s','%s',%lld);", req.UserID, req.Type, req.DeviceID.c_str(), req.NickName.c_str(), req.CreateTime);
		mysql->StartTransaction();
		mysql->Execute(sql);
		mysql->EndTransaction(true);
	}

	void LoginModule::OnUpdateUserTimeRequest(NetDataItem* item)
	{
		SDUpdateUserTimeRequest req;
		req.Unpack(item->data, 0);
		::printf("LoginModule::OnUpdateUserTimeRequest user_id:%d\n", req.UserID);

		//保存玩家
		MySQLWrap* mysql = GetMySQL();
		char sql[128];
		//`sp_update_user_time`(IN `user_id` int,IN `in_last_login` bigint,IN `in_last_quit` bigint)
		::sprintf(sql, "call sp_update_user_time(%d,%lld,%lld);", req.UserID, req.LastLogin, req.LastQuit);
		mysql->StartTransaction();
		mysql->Execute(sql);
		mysql->EndTransaction(true);
	}
}